﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Xceed.Silverlight.Utils;
using System.ComponentModel;
using Xceed.Silverlight.ListBox.PositioningFunction;

namespace Xceed.Silverlight.ListBox
{
  public class PathConfiguration : DependencyObject, ISupportInitialize
  {
    #region ContainerOrientation Property

    public static readonly DependencyProperty ContainerOrientationProperty = DependencyProperty.Register(
      "ContainerOrientation",
      typeof( ContainerOrientation ),
      typeof( PathConfiguration ),
      new PropertyMetadata( ContainerOrientation.None ) );

    public ContainerOrientation ContainerOrientation
    {
      get
      {
        return ( ContainerOrientation )this.GetValue( PathConfiguration.ContainerOrientationProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.ContainerOrientationProperty, value );
      }
    }

    #endregion

    #region ContainerPadding Property

    public static readonly DependencyProperty ContainerPaddingProperty = DependencyProperty.Register(
      "ContainerPadding",
      typeof( double ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        10.0d,
        new PropertyChangedCallback( PathConfiguration.OnContainerPaddingChanged ) ) );

    public double ContainerPadding
    {
      get
      {
        return ( double )this.GetValue( PathConfiguration.ContainerPaddingProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.ContainerPaddingProperty, value );
      }
    }

    private static void OnContainerPaddingChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathGeometry Public Property

    public static readonly DependencyProperty PathGeometryProperty = DependencyProperty.Register(
      "PathGeometry",
      typeof( Geometry ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        null,
        new PropertyChangedCallback( PathConfiguration.OnPathGeometryChanged ) ) );

    public Geometry PathGeometry
    {
      get
      {
        return ( Geometry )this.GetValue( PathConfiguration.PathGeometryProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathGeometryProperty, value );
      }
    }

    private static void OnPathGeometryChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      if( e.NewValue is GeometryGroup )
      {
        throw new NotSupportedException( "GeometryGroup is not supported." );
      }

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathPadding Property

    public static readonly DependencyProperty PathPaddingProperty = DependencyProperty.Register(
      "PathPadding",
      typeof( Thickness ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        new Thickness(),
        new PropertyChangedCallback( PathConfiguration.OnPathPaddingChanged ) ) );

    public Thickness PathPadding
    {
      get
      {
        return ( Thickness )this.GetValue( PathConfiguration.PathPaddingProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathPaddingProperty, value );
      }
    }

    private static void OnPathPaddingChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region PathStretch Property

    public static readonly DependencyProperty PathStretchProperty = DependencyProperty.Register(
      "PathStretch",
      typeof( Stretch ),
      typeof( PathConfiguration ),
      new PropertyMetadata(
        Stretch.Fill,
        new PropertyChangedCallback( PathConfiguration.OnPathStretchChanged ) ) );

    public Stretch PathStretch
    {
      get
      {
        return ( Stretch )this.GetValue( PathConfiguration.PathStretchProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.PathStretchProperty, value );
      }
    }

    private static void OnPathStretchChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      PathConfiguration pathConfiguration = sender as PathConfiguration;

      if( pathConfiguration == null )
        return;

      pathConfiguration.UpdateFlattenPathGeometry();
    }

    #endregion

    #region StartOffset Property

    public static readonly DependencyProperty StartOffsetProperty = DependencyProperty.Register(
      "StartOffset",
      typeof( double ),
      typeof( PathConfiguration ),
      new PropertyMetadata( ( double )0.0d ) );

    public double StartOffset
    {
      get
      {
        return ( double )this.GetValue( PathConfiguration.StartOffsetProperty );
      }
      set
      {
        this.SetValue( PathConfiguration.StartOffsetProperty, value );
      }
    }

    #endregion

    #region ScalingStops Public Property

    public ObservableCollection<ScalingStop> ScalingStops
    {
      get
      {
        if( m_scalingStops == null )
          m_scalingStops = new ObservableCollection<ScalingStop>();

        return m_scalingStops;
      }
    }

    private ObservableCollection<ScalingStop> m_scalingStops;

    #endregion

    #region ZIndexStops Public Property

    public ObservableCollection<ZIndexStop> ZIndexStops
    {
      get
      {
        if( m_zIndexStops == null )
          m_zIndexStops = new ObservableCollection<ZIndexStop>();

        return m_zIndexStops;
      }
    }

    private ObservableCollection<ZIndexStop> m_zIndexStops;

    #endregion


    #region FlattenedGeometry Private Property

    private PathGeometry FlattenedGeometry
    {
      get
      {
        if( ( m_flattenedGeometry == null ) || ( !m_flattenedGeometryIsValid ) )
        {
          m_flattenedGeometry = m_pathGeometryHelper.FlattenGeometry( this.PathGeometry, 30 );
          m_flattenedGeometryIsValid = true;
        }

        return m_flattenedGeometry;
      }
    }

    #endregion

    #region PointsOnFlattenedPathGeometry Private Property

    private List<Point> PointsOnFlattenedPathGeometry
    {
      get
      {
        if( m_pointsOnFlattenedPathGeometry == null )
        {
          m_pointsOnFlattenedPathGeometry = new List<Point>();
        }

        if( !m_pointsOnFlattendPathGeometryIsValid )
        {
          m_pointsOnFlattenedPathGeometry = GeometryHelper.GetPointsOnFlattenedPath( this.FlattenedGeometry );
          m_pointsOnFlattendPathGeometryIsValid = true;
        }

        return m_pointsOnFlattenedPathGeometry;
      }
    }

    #endregion


    #region ScalingFunction Internal Property

    internal IScalingFunction ScalingFunction
    {
      get
      {
        return m_scalingFunction;
      }
      set
      {
        if( m_scalingFunction != value )
        {
          m_scalingFunction = value;

          if( !this.IsInitializing )
            this.RaiseScalingFunctionChanged();
        }
      }
    }

    internal event EventHandler ScalingFunctionChanged;

    internal void RaiseScalingFunctionChanged()
    {
      if( this.ScalingFunctionChanged != null )
        this.ScalingFunctionChanged( this, EventArgs.Empty );
    }

    #endregion

    #region ZIndexFunction Internal Property

    internal IZIndexFunction ZIndexFunction
    {
      get
      {
        return m_zIndexFunction;
      }
      set
      {
        if( m_zIndexFunction != value )
        {
          m_zIndexFunction = value;

          if( !this.IsInitializing )
            this.RaiseZIndexFunctionChanged();
        }
      }
    }

    internal event EventHandler ZIndexFunctionChanged;

    internal void RaiseZIndexFunctionChanged()
    {
      if( this.ZIndexFunctionChanged != null )
        this.ZIndexFunctionChanged( this, EventArgs.Empty );
    }

    #endregion

    #region PositioningFunction Internal Property

    internal PositioningFunctionBase PositioningFunction
    {
      get
      {
        return m_positioningFunction;
      }
      set
      {
        if( m_positioningFunction != value )
        {
          m_positioningFunction = value;

          if( !this.IsInitializing )
            this.RaisePositioningFunctionChanged();
        }
      }
    }

    internal event EventHandler PositioningFunctionChanged;

    internal void RaisePositioningFunctionChanged()
    {
      if( this.PositioningFunctionChanged != null )
        this.PositioningFunctionChanged( this, EventArgs.Empty );
    }

    #endregion


    #region Private Methods

    private void UpdateFlattenPathGeometry()
    {
      if( this.IsInitializing )
        return;

      m_flattenedGeometryIsValid = false;
      m_pointsOnFlattendPathGeometryIsValid = false;

      this.UpdatePositioningFunction();
    }

    private void UpdatePositioningFunction()
    {
      this.PositioningFunction = new PathPositioning( this.PointsOnFlattenedPathGeometry,
        this.PathGeometry.Bounds,
        this.PathPadding,
        this.PathStretch,
        this.ContainerOrientation,
        this.ContainerPadding,
        this.StartOffset );
    }

    private void UpdateScalingFunction()
    {
      ObservableCollection<ScalingStop> scalingStops = this.ScalingStops;

      if( ( scalingStops == null ) || ( scalingStops.Count == 0 ) )
      {
        this.ScalingFunction = null;
      }
      else
      {
        this.ScalingFunction = new ScalingStopFunction()
        {
          ScalingStops = scalingStops
        };
      }
    }

    private void UpdateZIndexFunction()
    {
      ObservableCollection<ZIndexStop> zIndexStops = this.ZIndexStops;

      if( ( zIndexStops == null ) || ( zIndexStops.Count == 0 ) )
      {
        this.ZIndexFunction = null;
      }
      else
      {
        this.ZIndexFunction = new ZIndexFunction()
        {
          ZIndexStops = zIndexStops
        };
      }
    }

    #endregion


    #region ISupportInitialize Members

    void ISupportInitialize.BeginInit()
    {
      m_initializingCount++;
      this.BeginInitCore();
    }

    void ISupportInitialize.EndInit()
    {
      if( m_initializingCount == 0 )
        throw new InvalidOperationException( "An attempt has been made to call EndInit when no call to BeginInit has been made." );

      m_initializingCount--;

      if( m_initializingCount == 0 )
        this.EndInitCore();
    }

    internal bool IsInitializing
    {
      get
      {
        return ( m_initializingCount > 0 );
      }
    }

    internal virtual void BeginInitCore()
    {
    }

    internal virtual void EndInitCore()
    {
      if( this.PathGeometry == null )
        throw new ListBoxException( "PathGeometry cannot be null." );

      this.UpdateFlattenPathGeometry();
      this.UpdateScalingFunction();
      this.UpdateZIndexFunction();
    }

    private int m_initializingCount;

    #endregion

    #region Private fields

    private PathGeometryHelper m_pathGeometryHelper = new PathGeometryHelper();
    private bool m_pointsOnFlattendPathGeometryIsValid = false;
    private List<Point> m_pointsOnFlattenedPathGeometry;
    private bool m_flattenedGeometryIsValid = false;
    private PathGeometry m_flattenedGeometry;

    private PositioningFunctionBase m_positioningFunction;
    private IScalingFunction m_scalingFunction;
    private IZIndexFunction m_zIndexFunction;

    #endregion
  }
}
